/*
 * Copyright 2002-2018 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.core.annotation;

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.springframework.core.BridgeMethodResolver;
import org.springframework.lang.Nullable;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

/**
 * General utility methods for finding annotations, meta-annotations, and
 * repeatable annotations on {@link AnnotatedElement AnnotatedElements}.
 *
 * <p>{@code AnnotatedElementUtils} defines the public API for Spring's
 * meta-annotation programming model with support for <em>annotation attribute
 * overrides</em>. If you do not need support for annotation attribute
 * overrides, consider using {@link AnnotationUtils} instead.
 *
 * <p>Note that the features of this class are not provided by the JDK's
 * introspection facilities themselves.
 *
 * <h3>Annotation Attribute Overrides</h3>
 * <p>Support for meta-annotations with <em>attribute overrides</em> in
 * <em>composed annotations</em> is provided by all variants of the
 * {@code getMergedAnnotationAttributes()}, {@code getMergedAnnotation()},
 * {@code getAllMergedAnnotations()}, {@code getMergedRepeatableAnnotations()},
 * {@code findMergedAnnotationAttributes()}, {@code findMergedAnnotation()},
 * {@code findAllMergedAnnotations()}, and {@code findMergedRepeatableAnnotations()}
 * methods.
 *
 * <h3>Find vs. Get Semantics</h3>
 * <p>The search algorithms used by methods in this class follow either
 * <em>find</em> or <em>get</em> semantics. Consult the javadocs for each
 * individual method for details on which search algorithm is used.
 *
 * <p><strong>Get semantics</strong> are limited to searching for annotations
 * that are either <em>present</em> on an {@code AnnotatedElement} (i.e.,
 * declared locally or {@linkplain java.lang.annotation.Inherited inherited})
 * or declared within the annotation hierarchy <em>above</em> the
 * {@code AnnotatedElement}.
 *
 * <p><strong>Find semantics</strong> are much more exhaustive, providing
 * <em>get semantics</em> plus support for the following:
 *
 * <ul>
 * <li>Searching on interfaces, if the annotated element is a class
 * <li>Searching on superclasses, if the annotated element is a class
 * <li>Resolving bridged methods, if the annotated element is a method
 * <li>Searching on methods in interfaces, if the annotated element is a method
 * <li>Searching on methods in superclasses, if the annotated element is a method
 * </ul>
 *
 * <h3>Support for {@code @Inherited}</h3>
 * <p>Methods following <em>get semantics</em> will honor the contract of
 * Java's {@link java.lang.annotation.Inherited @Inherited} annotation except
 * that locally declared annotations (including custom composed annotations)
 * will be favored over inherited annotations. In contrast, methods following
 * <em>find semantics</em> will completely ignore the presence of
 * {@code @Inherited} since the <em>find</em> search algorithm manually
 * traverses type and method hierarchies and thereby implicitly supports
 * annotation inheritance without the need for {@code @Inherited}.
 *
 * @author Phillip Webb
 * @author Juergen Hoeller
 * @author Sam Brannen
 * @see AliasFor
 * @see AnnotationAttributes
 * @see AnnotationUtils
 * @see BridgeMethodResolver
 * @since 4.0
 */
public class AnnotatedElementUtils {

    /**
     * {@code null} constant used to denote that the search algorithm should continue.
     */
    @Nullable
    private static final Boolean CONTINUE = null;

    private static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0];

    private static final Processor<Boolean> alwaysTrueAnnotationProcessor = new AlwaysTrueBooleanAnnotationProcessor();


    /**
     * Build an adapted {@link AnnotatedElement} for the given annotations,
     * typically for use with other methods on {@link AnnotatedElementUtils}.
     *
     * @param annotations the annotations to expose through the {@code AnnotatedElement}
     * @since 4.3
     */
    public static AnnotatedElement forAnnotations(final Annotation... annotations) {
        return new AnnotatedElement() {
            @Override
            @SuppressWarnings("unchecked")
            @Nullable
            public <T extends Annotation> T getAnnotation(Class<T> annotationClass) {
                for (Annotation ann : annotations) {
                    if (ann.annotationType() == annotationClass) {
                        return (T) ann;
                    }
                }
                return null;
            }

            @Override
            public Annotation[] getAnnotations() {
                return annotations;
            }

            @Override
            public Annotation[] getDeclaredAnnotations() {
                return annotations;
            }
        };
    }

    /**
     * Get the fully qualified class names of all meta-annotation types
     * <em>present</em> on the annotation (of the specified {@code annotationType})
     * on the supplied {@link AnnotatedElement}.
     * <p>This method follows <em>get semantics</em> as described in the
     * {@linkplain AnnotatedElementUtils class-level javadoc}.
     *
     * @param element the annotated element
     * @param annotationType the annotation type on which to find meta-annotations
     * @return the names of all meta-annotations present on the annotation,
     * or {@code null} if not found
     * @see #getMetaAnnotationTypes(AnnotatedElement, String)
     * @see #hasMetaAnnotationTypes
     * @since 4.2
     */
    public static Set<String> getMetaAnnotationTypes(AnnotatedElement element,
            Class<? extends Annotation> annotationType) {
        return getMetaAnnotationTypes(element, element.getAnnotation(annotationType));
    }

    /**
     * Get the fully qualified class names of all meta-annotation
     * types <em>present</em> on the annotation (of the specified
     * {@code annotationName}) on the supplied {@link AnnotatedElement}.
     * <p>This method follows <em>get semantics</em> as described in the
     * {@linkplain AnnotatedElementUtils class-level javadoc}.
     *
     * @param element the annotated element
     * @param annotationName the fully qualified class name of the annotation
     * type on which to find meta-annotations
     * @return the names of all meta-annotations present on the annotation,
     * or an empty set if none found
     * @see #getMetaAnnotationTypes(AnnotatedElement, Class)
     * @see #hasMetaAnnotationTypes
     */
    public static Set<String> getMetaAnnotationTypes(AnnotatedElement element, String annotationName) {
        return getMetaAnnotationTypes(element, AnnotationUtils.getAnnotation(element, annotationName));
    }

    private static Set<String> getMetaAnnotationTypes(AnnotatedElement element, @Nullable Annotation composed) {
        if (composed == null) {
            return Collections.emptySet();
        }

        try {
            final Set<String> types = new LinkedHashSet<>();
            searchWithGetSemantics(composed.annotationType(), null, null, null,
                    new SimpleAnnotationProcessor<Object>(true) {
                        @Override
                        @Nullable
                        public Object process(@Nullable AnnotatedElement annotatedElement, Annotation annotation,
                                int metaDepth) {
                            types.add(annotation.annotationType().getName());
                            return CONTINUE;
                        }
                    }, new HashSet<>(), 1);
            return types;
        } catch (Throwable ex) {
            AnnotationUtils.rethrowAnnotationConfigurationException(ex);
            throw new IllegalStateException("Failed to introspect annotations on " + element, ex);
        }
    }

    /**
     * Determine if the supplied {@link AnnotatedElement} is annotated with
     * a <em>composed annotation</em> that is meta-annotated with an
     * annotation of the specified {@code annotationType}.
     * <p>This method follows <em>get semantics</em> as described in the
     * {@linkplain AnnotatedElementUtils class-level javadoc}.
     *
     * @param element the annotated element
     * @param annotationType the meta-annotation type to find
     * @return {@code true} if a matching meta-annotation is present
     * @see #getMetaAnnotationTypes
     * @since 4.2.3
     */
    public static boolean hasMetaAnnotationTypes(AnnotatedElement element, Class<? extends Annotation> annotationType) {
        return hasMetaAnnotationTypes(element, annotationType, null);
    }

    /**
     * Determine if the supplied {@link AnnotatedElement} is annotated with a
     * <em>composed annotation</em> that is meta-annotated with an annotation
     * of the specified {@code annotationName}.
     * <p>This method follows <em>get semantics</em> as described in the
     * {@linkplain AnnotatedElementUtils class-level javadoc}.
     *
     * @param element the annotated element
     * @param annotationName the fully qualified class name of the
     * meta-annotation type to find
     * @return {@code true} if a matching meta-annotation is present
     * @see #getMetaAnnotationTypes
     */
    public static boolean hasMetaAnnotationTypes(AnnotatedElement element, String annotationName) {
        return hasMetaAnnotationTypes(element, null, annotationName);
    }

    private static boolean hasMetaAnnotationTypes(AnnotatedElement element,
            @Nullable Class<? extends Annotation> annotationType,
            @Nullable String annotationName) {

        return Boolean.TRUE.equals(
                searchWithGetSemantics(element, annotationType, annotationName,
                        new SimpleAnnotationProcessor<Boolean>() {

                            @Override
                            @Nullable
                            public Boolean process(@Nullable AnnotatedElement annotatedElement, Annotation annotation,
                                    int metaDepth) {
                                return (metaDepth > 0 ? Boolean.TRUE : CONTINUE);
                            }
                        }));
    }

    /**
     * Determine if an annotation of the specified {@code annotationType}
     * is <em>present</em> on the supplied {@link AnnotatedElement} or
     * within the annotation hierarchy <em>above</em> the specified element.
     * <p>If this method returns {@code true}, then {@link #getMergedAnnotationAttributes}
     * will return a non-null value.
     * <p>This method follows <em>get semantics</em> as described in the
     * {@linkplain AnnotatedElementUtils class-level javadoc}.
     *
     * @param element the annotated element
     * @param annotationType the annotation type to find
     * @return {@code true} if a matching annotation is present
     * @see #hasAnnotation(AnnotatedElement, Class)
     * @since 4.2.3
     */
    public static boolean isAnnotated(AnnotatedElement element, Class<? extends Annotation> annotationType) {
        // Shortcut: directly present on the element, with no processing needed?
        if (element.isAnnotationPresent(annotationType)) {
            return true;
        }
        return Boolean.TRUE.equals(
                searchWithGetSemantics(element, annotationType, null, alwaysTrueAnnotationProcessor));
    }

    /**
     * Determine if an annotation of the specified {@code annotationName} is
     * <em>present</em> on the supplied {@link AnnotatedElement} or within the
     * annotation hierarchy <em>above</em> the specified element.
     * <p>If this method returns {@code true}, then {@link #getMergedAnnotationAttributes}
     * will return a non-null value.
     * <p>This method follows <em>get semantics</em> as described in the
     * {@linkplain AnnotatedElementUtils class-level javadoc}.
     *
     * @param element the annotated element
     * @param annotationName the fully qualified class name of the annotation type to find
     * @return {@code true} if a matching annotation is present
     */
    public static boolean isAnnotated(AnnotatedElement element, String annotationName) {
        return Boolean.TRUE.equals(
                searchWithGetSemantics(element, null, annotationName, alwaysTrueAnnotationProcessor));
    }

    /**
     * Get the first annotation of the specified {@code annotationType} within
     * the annotation hierarchy <em>above</em> the supplied {@code element} and
     * merge that annotation's attributes with <em>matching</em> attributes from
     * annotations in lower levels of the annotation hierarchy.
     * <p>{@link AliasFor @AliasFor} semantics are fully supported, both
     * within a single annotation and within the annotation hierarchy.
     * <p>This method delegates to {@link #getMergedAnnotationAttributes(AnnotatedElement, String)}.
     *
     * @param element the annotated element
     * @param annotationType the annotation type to find
     * @return the merged {@code AnnotationAttributes}, or {@code null} if not found
     * @see #getMergedAnnotationAttributes(AnnotatedElement, String, boolean, boolean)
     * @see #findMergedAnnotationAttributes(AnnotatedElement, String, boolean, boolean)
     * @see #getMergedAnnotation(AnnotatedElement, Class)
     * @see #findMergedAnnotation(AnnotatedElement, Class)
     * @since 4.2
     */
    @Nullable
    public static AnnotationAttributes getMergedAnnotationAttributes(
            AnnotatedElement element, Class<? extends Annotation> annotationType) {

        AnnotationAttributes attributes = searchWithGetSemantics(element, annotationType, null,
                new MergedAnnotationAttributesProcessor());
        AnnotationUtils.postProcessAnnotationAttributes(element, attributes, false, false);
        return attributes;
    }

    /**
     * Get the first annotation of the specified {@code annotationName} within
     * the annotation hierarchy <em>above</em> the supplied {@code element} and
     * merge that annotation's attributes with <em>matching</em> attributes from
     * annotations in lower levels of the annotation hierarchy.
     * <p>{@link AliasFor @AliasFor} semantics are fully supported, both
     * within a single annotation and within the annotation hierarchy.
     * <p>This method delegates to {@link #getMergedAnnotationAttributes(AnnotatedElement, String, boolean, boolean)},
     * supplying {@code false} for {@code classValuesAsString} and {@code nestedAnnotationsAsMap}.
     *
     * @param element the annotated element
     * @param annotationName the fully qualified class name of the annotation type to find
     * @return the merged {@code AnnotationAttributes}, or {@code null} if not found
     * @see #getMergedAnnotationAttributes(AnnotatedElement, String, boolean, boolean)
     * @see #findMergedAnnotationAttributes(AnnotatedElement, String, boolean, boolean)
     * @see #findMergedAnnotation(AnnotatedElement, Class)
     * @see #getAllAnnotationAttributes(AnnotatedElement, String)
     * @since 4.2
     */
    @Nullable
    public static AnnotationAttributes getMergedAnnotationAttributes(AnnotatedElement element, String annotationName) {
        return getMergedAnnotationAttributes(element, annotationName, false, false);
    }

    /**
     * Get the first annotation of the specified {@code annotationName} within
     * the annotation hierarchy <em>above</em> the supplied {@code element} and
     * merge that annotation's attributes with <em>matching</em> attributes from
     * annotations in lower levels of the annotation hierarchy.
     * <p>Attributes from lower levels in the annotation hierarchy override attributes
     * of the same name from higher levels, and {@link AliasFor @AliasFor} semantics are
     * fully supported, both within a single annotation and within the annotation hierarchy.
     * <p>In contrast to {@link #getAllAnnotationAttributes}, the search algorithm used by
     * this method will stop searching the annotation hierarchy once the first annotation
     * of the specified {@code annotationName} has been found. As a consequence,
     * additional annotations of the specified {@code annotationName} will be ignored.
     * <p>This method follows <em>get semantics</em> as described in the
     * {@linkplain AnnotatedElementUtils class-level javadoc}.
     *
     * @param element the annotated element
     * @param annotationName the fully qualified class name of the annotation type to find
     * @param classValuesAsString whether to convert Class references into Strings or to
     * preserve them as Class references
     * @param nestedAnnotationsAsMap whether to convert nested Annotation instances
     * into {@code AnnotationAttributes} maps or to preserve them as Annotation instances
     * @return the merged {@code AnnotationAttributes}, or {@code null} if not found
     * @see #findMergedAnnotation(AnnotatedElement, Class)
     * @see #findMergedAnnotationAttributes(AnnotatedElement, String, boolean, boolean)
     * @see #getAllAnnotationAttributes(AnnotatedElement, String, boolean, boolean)
     * @since 4.2
     */
    @Nullable
    public static AnnotationAttributes getMergedAnnotationAttributes(AnnotatedElement element,
            String annotationName, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {

        AnnotationAttributes attributes = searchWithGetSemantics(element, null, annotationName,
                new MergedAnnotationAttributesProcessor(classValuesAsString, nestedAnnotationsAsMap));
        AnnotationUtils.postProcessAnnotationAttributes(element, attributes, classValuesAsString,
                nestedAnnotationsAsMap);
        return attributes;
    }

    /**
     * Get the first annotation of the specified {@code annotationType} within
     * the annotation hierarchy <em>above</em> the supplied {@code element},
     * merge that annotation's attributes with <em>matching</em> attributes from
     * annotations in lower levels of the annotation hierarchy, and synthesize
     * the result back into an annotation of the specified {@code annotationType}.
     * <p>{@link AliasFor @AliasFor} semantics are fully supported, both
     * within a single annotation and within the annotation hierarchy.
     * <p>This method delegates to {@link #getMergedAnnotationAttributes(AnnotatedElement, Class)}
     * and {@link AnnotationUtils#synthesizeAnnotation(Map, Class, AnnotatedElement)}.
     *
     * @param element the annotated element
     * @param annotationType the annotation type to find
     * @return the merged, synthesized {@code Annotation}, or {@code null} if not found
     * @see #getMergedAnnotationAttributes(AnnotatedElement, Class)
     * @see #findMergedAnnotation(AnnotatedElement, Class)
     * @see AnnotationUtils#synthesizeAnnotation(Map, Class, AnnotatedElement)
     * @since 4.2
     */
    @Nullable
    public static <A extends Annotation> A getMergedAnnotation(AnnotatedElement element, Class<A> annotationType) {
        // Shortcut: directly present on the element, with no merging needed?
        if (!(element instanceof Class)) {
            // Do not use this shortcut against a Class: Inherited annotations
            // would get preferred over locally declared composed annotations.
            A annotation = element.getAnnotation(annotationType);
            if (annotation != null) {
                return AnnotationUtils.synthesizeAnnotation(annotation, element);
            }
        }

        // Exhaustive retrieval of merged annotation attributes...
        AnnotationAttributes attributes = getMergedAnnotationAttributes(element, annotationType);
        return (attributes != null ? AnnotationUtils.synthesizeAnnotation(attributes, annotationType, element) : null);
    }

    /**
     * Get <strong>all</strong> annotations of the specified {@code annotationType}
     * within the annotation hierarchy <em>above</em> the supplied {@code element};
     * and for each annotation found, merge that annotation's attributes with
     * <em>matching</em> attributes from annotations in lower levels of the annotation
     * hierarchy and synthesize the results back into an annotation of the specified
     * {@code annotationType}.
     * <p>{@link AliasFor @AliasFor} semantics are fully supported, both within a
     * single annotation and within annotation hierarchies.
     * <p>This method follows <em>get semantics</em> as described in the
     * {@linkplain AnnotatedElementUtils class-level javadoc}.
     *
     * @param element the annotated element (never {@code null})
     * @param annotationType the annotation type to find (never {@code null})
     * @return the set of all merged, synthesized {@code Annotations} found,
     * or an empty set if none were found
     * @see #getMergedAnnotation(AnnotatedElement, Class)
     * @see #getAllAnnotationAttributes(AnnotatedElement, String)
     * @see #findAllMergedAnnotations(AnnotatedElement, Class)
     * @since 4.3
     */
    public static <A extends Annotation> Set<A> getAllMergedAnnotations(AnnotatedElement element,
            Class<A> annotationType) {
        MergedAnnotationAttributesProcessor processor = new MergedAnnotationAttributesProcessor(false, false, true);
        searchWithGetSemantics(element, annotationType, null, processor);
        return postProcessAndSynthesizeAggregatedResults(element, annotationType, processor.getAggregatedResults());
    }

    /**
     * Get all <em>repeatable annotations</em> of the specified {@code annotationType}
     * within the annotation hierarchy <em>above</em> the supplied {@code element};
     * and for each annotation found, merge that annotation's attributes with
     * <em>matching</em> attributes from annotations in lower levels of the annotation
     * hierarchy and synthesize the results back into an annotation of the specified
     * {@code annotationType}.
     * <p>The container type that holds the repeatable annotations will be looked up
     * via {@link java.lang.annotation.Repeatable}.
     * <p>{@link AliasFor @AliasFor} semantics are fully supported, both within a
     * single annotation and within annotation hierarchies.
     * <p>This method follows <em>get semantics</em> as described in the
     * {@linkplain AnnotatedElementUtils class-level javadoc}.
     *
     * @param element the annotated element (never {@code null})
     * @param annotationType the annotation type to find (never {@code null})
     * @return the set of all merged repeatable {@code Annotations} found,
     * or an empty set if none were found
     * @throws IllegalArgumentException if the {@code element} or {@code annotationType}
     * is {@code null}, or if the container type cannot be resolved
     * @see #getMergedAnnotation(AnnotatedElement, Class)
     * @see #getAllMergedAnnotations(AnnotatedElement, Class)
     * @see #getMergedRepeatableAnnotations(AnnotatedElement, Class, Class)
     * @since 4.3
     */
    public static <A extends Annotation> Set<A> getMergedRepeatableAnnotations(AnnotatedElement element,
            Class<A> annotationType) {

        return getMergedRepeatableAnnotations(element, annotationType, null);
    }

    /**
     * Get all <em>repeatable annotations</em> of the specified {@code annotationType}
     * within the annotation hierarchy <em>above</em> the supplied {@code element};
     * and for each annotation found, merge that annotation's attributes with
     * <em>matching</em> attributes from annotations in lower levels of the annotation
     * hierarchy and synthesize the results back into an annotation of the specified
     * {@code annotationType}.
     * <p>{@link AliasFor @AliasFor} semantics are fully supported, both within a
     * single annotation and within annotation hierarchies.
     * <p>This method follows <em>get semantics</em> as described in the
     * {@linkplain AnnotatedElementUtils class-level javadoc}.
     *
     * @param element the annotated element (never {@code null})
     * @param annotationType the annotation type to find (never {@code null})
     * @param containerType the type of the container that holds the annotations;
     * may be {@code null} if the container type should be looked up via
     * {@link java.lang.annotation.Repeatable}
     * @return the set of all merged repeatable {@code Annotations} found,
     * or an empty set if none were found
     * @throws IllegalArgumentException if the {@code element} or {@code annotationType}
     * is {@code null}, or if the container type cannot be resolved
     * @throws AnnotationConfigurationException if the supplied {@code containerType}
     * is not a valid container annotation for the supplied {@code annotationType}
     * @see #getMergedAnnotation(AnnotatedElement, Class)
     * @see #getAllMergedAnnotations(AnnotatedElement, Class)
     * @since 4.3
     */
    public static <A extends Annotation> Set<A> getMergedRepeatableAnnotations(AnnotatedElement element,
            Class<A> annotationType, @Nullable Class<? extends Annotation> containerType) {

        if (containerType == null) {
            containerType = resolveContainerType(annotationType);
        } else {
            validateContainerType(annotationType, containerType);
        }

        MergedAnnotationAttributesProcessor processor = new MergedAnnotationAttributesProcessor(false, false, true);
        searchWithGetSemantics(element, annotationType, null, containerType, processor);
        return postProcessAndSynthesizeAggregatedResults(element, annotationType, processor.getAggregatedResults());
    }

    /**
     * Get the annotation attributes of <strong>all</strong> annotations of the specified
     * {@code annotationName} in the annotation hierarchy above the supplied
     * {@link AnnotatedElement} and store the results in a {@link MultiValueMap}.
     * <p>Note: in contrast to {@link #getMergedAnnotationAttributes(AnnotatedElement, String)},
     * this method does <em>not</em> support attribute overrides.
     * <p>This method follows <em>get semantics</em> as described in the
     * {@linkplain AnnotatedElementUtils class-level javadoc}.
     *
     * @param element the annotated element
     * @param annotationName the fully qualified class name of the annotation type to find
     * @return a {@link MultiValueMap} keyed by attribute name, containing the annotation
     * attributes from all annotations found, or {@code null} if not found
     * @see #getAllAnnotationAttributes(AnnotatedElement, String, boolean, boolean)
     */
    @Nullable
    public static MultiValueMap<String, Object> getAllAnnotationAttributes(AnnotatedElement element,
            String annotationName) {
        return getAllAnnotationAttributes(element, annotationName, false, false);
    }

    /**
     * Get the annotation attributes of <strong>all</strong> annotations of
     * the specified {@code annotationName} in the annotation hierarchy above
     * the supplied {@link AnnotatedElement} and store the results in a
     * {@link MultiValueMap}.
     * <p>Note: in contrast to {@link #getMergedAnnotationAttributes(AnnotatedElement, String)},
     * this method does <em>not</em> support attribute overrides.
     * <p>This method follows <em>get semantics</em> as described in the
     * {@linkplain AnnotatedElementUtils class-level javadoc}.
     *
     * @param element the annotated element
     * @param annotationName the fully qualified class name of the annotation type to find
     * @param classValuesAsString whether to convert Class references into Strings or to
     * preserve them as Class references
     * @param nestedAnnotationsAsMap whether to convert nested Annotation instances into
     * {@code AnnotationAttributes} maps or to preserve them as Annotation instances
     * @return a {@link MultiValueMap} keyed by attribute name, containing the annotation
     * attributes from all annotations found, or {@code null} if not found
     */
    @Nullable
    public static MultiValueMap<String, Object> getAllAnnotationAttributes(AnnotatedElement element,
            String annotationName, final boolean classValuesAsString, final boolean nestedAnnotationsAsMap) {

        final MultiValueMap<String, Object> attributesMap = new LinkedMultiValueMap<>();

        searchWithGetSemantics(element, null, annotationName, new SimpleAnnotationProcessor<Object>() {
            @Override
            @Nullable
            public Object process(@Nullable AnnotatedElement annotatedElement, Annotation annotation, int metaDepth) {
                AnnotationAttributes annotationAttributes = AnnotationUtils.getAnnotationAttributes(
                        annotation, classValuesAsString, nestedAnnotationsAsMap);
                annotationAttributes.forEach(attributesMap::add);
                return CONTINUE;
            }
        });

        return (!attributesMap.isEmpty() ? attributesMap : null);
    }

    /**
     * Determine if an annotation of the specified {@code annotationType}
     * is <em>available</em> on the supplied {@link AnnotatedElement} or
     * within the annotation hierarchy <em>above</em> the specified element.
     * <p>If this method returns {@code true}, then {@link #findMergedAnnotationAttributes}
     * will return a non-null value.
     * <p>This method follows <em>find semantics</em> as described in the
     * {@linkplain AnnotatedElementUtils class-level javadoc}.
     *
     * @param element the annotated element
     * @param annotationType the annotation type to find
     * @return {@code true} if a matching annotation is present
     * @see #isAnnotated(AnnotatedElement, Class)
     * @since 4.3
     */
    public static boolean hasAnnotation(AnnotatedElement element, Class<? extends Annotation> annotationType) {
        // Shortcut: directly present on the element, with no processing needed?
        if (element.isAnnotationPresent(annotationType)) {
            return true;
        }
        return Boolean.TRUE.equals(
                searchWithFindSemantics(element, annotationType, null, alwaysTrueAnnotationProcessor));
    }

    /**
     * Find the first annotation of the specified {@code annotationType} within
     * the annotation hierarchy <em>above</em> the supplied {@code element} and
     * merge that annotation's attributes with <em>matching</em> attributes from
     * annotations in lower levels of the annotation hierarchy.
     * <p>Attributes from lower levels in the annotation hierarchy override
     * attributes of the same name from higher levels, and
     * {@link AliasFor @AliasFor} semantics are fully supported, both
     * within a single annotation and within the annotation hierarchy.
     * <p>In contrast to {@link #getAllAnnotationAttributes}, the search
     * algorithm used by this method will stop searching the annotation
     * hierarchy once the first annotation of the specified
     * {@code annotationType} has been found. As a consequence, additional
     * annotations of the specified {@code annotationType} will be ignored.
     * <p>This method follows <em>find semantics</em> as described in the
     * {@linkplain AnnotatedElementUtils class-level javadoc}.
     *
     * @param element the annotated element
     * @param annotationType the annotation type to find
     * @param classValuesAsString whether to convert Class references into
     * Strings or to preserve them as Class references
     * @param nestedAnnotationsAsMap whether to convert nested Annotation
     * instances into {@code AnnotationAttributes} maps or to preserve them
     * as Annotation instances
     * @return the merged {@code AnnotationAttributes}, or {@code null} if
     * not found
     * @see #findMergedAnnotation(AnnotatedElement, Class)
     * @see #getMergedAnnotationAttributes(AnnotatedElement, String, boolean, boolean)
     * @since 4.2
     */
    @Nullable
    public static AnnotationAttributes findMergedAnnotationAttributes(AnnotatedElement element,
            Class<? extends Annotation> annotationType, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {

        AnnotationAttributes attributes = searchWithFindSemantics(element, annotationType, null,
                new MergedAnnotationAttributesProcessor(classValuesAsString, nestedAnnotationsAsMap));
        AnnotationUtils.postProcessAnnotationAttributes(element, attributes, classValuesAsString,
                nestedAnnotationsAsMap);
        return attributes;
    }

    /**
     * Find the first annotation of the specified {@code annotationName} within
     * the annotation hierarchy <em>above</em> the supplied {@code element} and
     * merge that annotation's attributes with <em>matching</em> attributes from
     * annotations in lower levels of the annotation hierarchy.
     * <p>Attributes from lower levels in the annotation hierarchy override
     * attributes of the same name from higher levels, and
     * {@link AliasFor @AliasFor} semantics are fully supported, both
     * within a single annotation and within the annotation hierarchy.
     * <p>In contrast to {@link #getAllAnnotationAttributes}, the search
     * algorithm used by this method will stop searching the annotation
     * hierarchy once the first annotation of the specified
     * {@code annotationName} has been found. As a consequence, additional
     * annotations of the specified {@code annotationName} will be ignored.
     * <p>This method follows <em>find semantics</em> as described in the
     * {@linkplain AnnotatedElementUtils class-level javadoc}.
     *
     * @param element the annotated element
     * @param annotationName the fully qualified class name of the annotation type to find
     * @param classValuesAsString whether to convert Class references into Strings or to
     * preserve them as Class references
     * @param nestedAnnotationsAsMap whether to convert nested Annotation instances into
     * {@code AnnotationAttributes} maps or to preserve them as Annotation instances
     * @return the merged {@code AnnotationAttributes}, or {@code null} if not found
     * @see #findMergedAnnotation(AnnotatedElement, Class)
     * @see #getMergedAnnotationAttributes(AnnotatedElement, String, boolean, boolean)
     * @since 4.2
     */
    @Nullable
    public static AnnotationAttributes findMergedAnnotationAttributes(AnnotatedElement element,
            String annotationName, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {

        AnnotationAttributes attributes = searchWithFindSemantics(element, null, annotationName,
                new MergedAnnotationAttributesProcessor(classValuesAsString, nestedAnnotationsAsMap));
        AnnotationUtils.postProcessAnnotationAttributes(element, attributes, classValuesAsString,
                nestedAnnotationsAsMap);
        return attributes;
    }

    /**
     * Find the first annotation of the specified {@code annotationType} within
     * the annotation hierarchy <em>above</em> the supplied {@code element},
     * merge that annotation's attributes with <em>matching</em> attributes from
     * annotations in lower levels of the annotation hierarchy, and synthesize
     * the result back into an annotation of the specified {@code annotationType}.
     * <p>{@link AliasFor @AliasFor} semantics are fully supported, both
     * within a single annotation and within the annotation hierarchy.
     * <p>This method follows <em>find semantics</em> as described in the
     * {@linkplain AnnotatedElementUtils class-level javadoc}.
     *
     * @param element the annotated element
     * @param annotationType the annotation type to find
     * @return the merged, synthesized {@code Annotation}, or {@code null} if not found
     * @see #findAllMergedAnnotations(AnnotatedElement, Class)
     * @see #findMergedAnnotationAttributes(AnnotatedElement, String, boolean, boolean)
     * @see #getMergedAnnotationAttributes(AnnotatedElement, Class)
     * @since 4.2
     */
    @Nullable
    public static <A extends Annotation> A findMergedAnnotation(AnnotatedElement element, Class<A> annotationType) {
        // Shortcut: directly present on the element, with no merging needed?
        if (!(element instanceof Class)) {
            // Do not use this shortcut against a Class: Inherited annotations
            // would get preferred over locally declared composed annotations.
            A annotation = element.getAnnotation(annotationType);
            if (annotation != null) {
                return AnnotationUtils.synthesizeAnnotation(annotation, element);
            }
        }

        // Exhaustive retrieval of merged annotation attributes...
        AnnotationAttributes attributes = findMergedAnnotationAttributes(element, annotationType, false, false);
        return (attributes != null ? AnnotationUtils.synthesizeAnnotation(attributes, annotationType, element) : null);
    }

    /**
     * Find <strong>all</strong> annotations of the specified {@code annotationType}
     * within the annotation hierarchy <em>above</em> the supplied {@code element};
     * and for each annotation found, merge that annotation's attributes with
     * <em>matching</em> attributes from annotations in lower levels of the annotation
     * hierarchy and synthesize the results back into an annotation of the specified
     * {@code annotationType}.
     * <p>{@link AliasFor @AliasFor} semantics are fully supported, both within a
     * single annotation and within annotation hierarchies.
     * <p>This method follows <em>find semantics</em> as described in the
     * {@linkplain AnnotatedElementUtils class-level javadoc}.
     *
     * @param element the annotated element (never {@code null})
     * @param annotationType the annotation type to find (never {@code null})
     * @return the set of all merged, synthesized {@code Annotations} found,
     * or an empty set if none were found
     * @see #findMergedAnnotation(AnnotatedElement, Class)
     * @see #getAllMergedAnnotations(AnnotatedElement, Class)
     * @since 4.3
     */
    public static <A extends Annotation> Set<A> findAllMergedAnnotations(AnnotatedElement element,
            Class<A> annotationType) {
        MergedAnnotationAttributesProcessor processor = new MergedAnnotationAttributesProcessor(false, false, true);
        searchWithFindSemantics(element, annotationType, null, processor);
        return postProcessAndSynthesizeAggregatedResults(element, annotationType, processor.getAggregatedResults());
    }

    /**
     * Find all <em>repeatable annotations</em> of the specified {@code annotationType}
     * within the annotation hierarchy <em>above</em> the supplied {@code element};
     * and for each annotation found, merge that annotation's attributes with
     * <em>matching</em> attributes from annotations in lower levels of the annotation
     * hierarchy and synthesize the results back into an annotation of the specified
     * {@code annotationType}.
     * <p>The container type that holds the repeatable annotations will be looked up
     * via {@link java.lang.annotation.Repeatable}.
     * <p>{@link AliasFor @AliasFor} semantics are fully supported, both within a
     * single annotation and within annotation hierarchies.
     * <p>This method follows <em>find semantics</em> as described in the
     * {@linkplain AnnotatedElementUtils class-level javadoc}.
     *
     * @param element the annotated element (never {@code null})
     * @param annotationType the annotation type to find (never {@code null})
     * @return the set of all merged repeatable {@code Annotations} found,
     * or an empty set if none were found
     * @throws IllegalArgumentException if the {@code element} or {@code annotationType}
     * is {@code null}, or if the container type cannot be resolved
     * @see #findMergedAnnotation(AnnotatedElement, Class)
     * @see #findAllMergedAnnotations(AnnotatedElement, Class)
     * @see #findMergedRepeatableAnnotations(AnnotatedElement, Class, Class)
     * @since 4.3
     */
    public static <A extends Annotation> Set<A> findMergedRepeatableAnnotations(AnnotatedElement element,
            Class<A> annotationType) {

        return findMergedRepeatableAnnotations(element, annotationType, null);
    }

    /**
     * Find all <em>repeatable annotations</em> of the specified {@code annotationType}
     * within the annotation hierarchy <em>above</em> the supplied {@code element};
     * and for each annotation found, merge that annotation's attributes with
     * <em>matching</em> attributes from annotations in lower levels of the annotation
     * hierarchy and synthesize the results back into an annotation of the specified
     * {@code annotationType}.
     * <p>{@link AliasFor @AliasFor} semantics are fully supported, both within a
     * single annotation and within annotation hierarchies.
     * <p>This method follows <em>find semantics</em> as described in the
     * {@linkplain AnnotatedElementUtils class-level javadoc}.
     *
     * @param element the annotated element (never {@code null})
     * @param annotationType the annotation type to find (never {@code null})
     * @param containerType the type of the container that holds the annotations;
     * may be {@code null} if the container type should be looked up via
     * {@link java.lang.annotation.Repeatable}
     * @return the set of all merged repeatable {@code Annotations} found,
     * or an empty set if none were found
     * @throws IllegalArgumentException if the {@code element} or {@code annotationType}
     * is {@code null}, or if the container type cannot be resolved
     * @throws AnnotationConfigurationException if the supplied {@code containerType}
     * is not a valid container annotation for the supplied {@code annotationType}
     * @see #findMergedAnnotation(AnnotatedElement, Class)
     * @see #findAllMergedAnnotations(AnnotatedElement, Class)
     * @since 4.3
     */
    public static <A extends Annotation> Set<A> findMergedRepeatableAnnotations(AnnotatedElement element,
            Class<A> annotationType, @Nullable Class<? extends Annotation> containerType) {

        if (containerType == null) {
            containerType = resolveContainerType(annotationType);
        } else {
            validateContainerType(annotationType, containerType);
        }

        MergedAnnotationAttributesProcessor processor = new MergedAnnotationAttributesProcessor(false, false, true);
        searchWithFindSemantics(element, annotationType, null, containerType, processor);
        return postProcessAndSynthesizeAggregatedResults(element, annotationType, processor.getAggregatedResults());
    }

    /**
     * Search for annotations of the specified {@code annotationName} or
     * {@code annotationType} on the specified {@code element}, following
     * <em>get semantics</em>.
     *
     * @param element the annotated element
     * @param annotationType the annotation type to find
     * @param annotationName the fully qualified class name of the annotation
     * type to find (as an alternative to {@code annotationType})
     * @param processor the processor to delegate to
     * @return the result of the processor, potentially {@code null}
     */
    @Nullable
    private static <T> T searchWithGetSemantics(AnnotatedElement element,
            @Nullable Class<? extends Annotation> annotationType,
            @Nullable String annotationName, Processor<T> processor) {

        return searchWithGetSemantics(element, annotationType, annotationName, null, processor);
    }

    /**
     * Search for annotations of the specified {@code annotationName} or
     * {@code annotationType} on the specified {@code element}, following
     * <em>get semantics</em>.
     *
     * @param element the annotated element
     * @param annotationType the annotation type to find
     * @param annotationName the fully qualified class name of the annotation
     * type to find (as an alternative to {@code annotationType})
     * @param containerType the type of the container that holds repeatable
     * annotations, or {@code null} if the annotation is not repeatable
     * @param processor the processor to delegate to
     * @return the result of the processor, potentially {@code null}
     * @since 4.3
     */
    @Nullable
    private static <T> T searchWithGetSemantics(AnnotatedElement element,
            @Nullable Class<? extends Annotation> annotationType, @Nullable String annotationName,
            @Nullable Class<? extends Annotation> containerType, Processor<T> processor) {

        try {
            return searchWithGetSemantics(element, annotationType, annotationName, containerType, processor,
                    new HashSet<>(), 0);
        } catch (Throwable ex) {
            AnnotationUtils.rethrowAnnotationConfigurationException(ex);
            throw new IllegalStateException("Failed to introspect annotations on " + element, ex);
        }
    }

    /**
     * Perform the search algorithm for the {@link #searchWithGetSemantics}
     * method, avoiding endless recursion by tracking which annotated elements
     * have already been <em>visited</em>.
     * <p>The {@code metaDepth} parameter is explained in the
     * {@link Processor#process process()} method of the {@link Processor} API.
     *
     * @param element the annotated element
     * @param annotationType the annotation type to find
     * @param annotationName the fully qualified class name of the annotation
     * type to find (as an alternative to {@code annotationType})
     * @param containerType the type of the container that holds repeatable
     * annotations, or {@code null} if the annotation is not repeatable
     * @param processor the processor to delegate to
     * @param visited the set of annotated elements that have already been visited
     * @param metaDepth the meta-depth of the annotation
     * @return the result of the processor, potentially {@code null}
     */
    @Nullable
    private static <T> T searchWithGetSemantics(AnnotatedElement element,
            @Nullable Class<? extends Annotation> annotationType, @Nullable String annotationName,
            @Nullable Class<? extends Annotation> containerType, Processor<T> processor,
            Set<AnnotatedElement> visited, int metaDepth) {

        if (visited.add(element)) {
            try {
                // Start searching within locally declared annotations
                List<Annotation> declaredAnnotations = Arrays.asList(element.getDeclaredAnnotations());
                T result = searchWithGetSemanticsInAnnotations(element, declaredAnnotations,
                        annotationType, annotationName, containerType, processor, visited, metaDepth);
                if (result != null) {
                    return result;
                }

                if (element instanceof Class) { // otherwise getAnnotations doesn't return anything new
                    List<Annotation> inheritedAnnotations = new ArrayList<>();
                    for (Annotation annotation : element.getAnnotations()) {
                        if (!declaredAnnotations.contains(annotation)) {
                            inheritedAnnotations.add(annotation);
                        }
                    }

                    // Continue searching within inherited annotations
                    result = searchWithGetSemanticsInAnnotations(element, inheritedAnnotations,
                            annotationType, annotationName, containerType, processor, visited, metaDepth);
                    if (result != null) {
                        return result;
                    }
                }
            } catch (Throwable ex) {
                AnnotationUtils.handleIntrospectionFailure(element, ex);
            }
        }

        return null;
    }

    /**
     * This method is invoked by {@link #searchWithGetSemantics} to perform
     * the actual search within the supplied list of annotations.
     * <p>This method should be invoked first with locally declared annotations
     * and then subsequently with inherited annotations, thereby allowing
     * local annotations to take precedence over inherited annotations.
     * <p>The {@code metaDepth} parameter is explained in the
     * {@link Processor#process process()} method of the {@link Processor} API.
     *
     * @param element the element that is annotated with the supplied
     * annotations, used for contextual logging; may be {@code null} if unknown
     * @param annotations the annotations to search in
     * @param annotationType the annotation type to find
     * @param annotationName the fully qualified class name of the annotation
     * type to find (as an alternative to {@code annotationType})
     * @param containerType the type of the container that holds repeatable
     * annotations, or {@code null} if the annotation is not repeatable
     * @param processor the processor to delegate to
     * @param visited the set of annotated elements that have already been visited
     * @param metaDepth the meta-depth of the annotation
     * @return the result of the processor, potentially {@code null}
     * @since 4.2
     */
    @Nullable
    private static <T> T searchWithGetSemanticsInAnnotations(@Nullable AnnotatedElement element,
            List<Annotation> annotations, @Nullable Class<? extends Annotation> annotationType,
            @Nullable String annotationName, @Nullable Class<? extends Annotation> containerType,
            Processor<T> processor, Set<AnnotatedElement> visited, int metaDepth) {

        // Search in annotations
        for (Annotation annotation : annotations) {
            Class<? extends Annotation> currentAnnotationType = annotation.annotationType();
            if (!AnnotationUtils.isInJavaLangAnnotationPackage(currentAnnotationType)) {
                if (currentAnnotationType == annotationType ||
                        currentAnnotationType.getName().equals(annotationName) ||
                        processor.alwaysProcesses()) {
                    T result = processor.process(element, annotation, metaDepth);
                    if (result != null) {
                        if (processor.aggregates() && metaDepth == 0) {
                            processor.getAggregatedResults().add(result);
                        } else {
                            return result;
                        }
                    }
                }
                // Repeatable annotations in container?
                else if (currentAnnotationType == containerType) {
                    for (Annotation contained : getRawAnnotationsFromContainer(element, annotation)) {
                        T result = processor.process(element, contained, metaDepth);
                        if (result != null) {
                            // No need to post-process since repeatable annotations within a
                            // container cannot be composed annotations.
                            processor.getAggregatedResults().add(result);
                        }
                    }
                }
            }
        }

        // Recursively search in meta-annotations
        for (Annotation annotation : annotations) {
            Class<? extends Annotation> currentAnnotationType = annotation.annotationType();
            if (!AnnotationUtils.isInJavaLangAnnotationPackage(currentAnnotationType)) {
                T result = searchWithGetSemantics(currentAnnotationType, annotationType,
                        annotationName, containerType, processor, visited, metaDepth + 1);
                if (result != null) {
                    processor.postProcess(element, annotation, result);
                    if (processor.aggregates() && metaDepth == 0) {
                        processor.getAggregatedResults().add(result);
                    } else {
                        return result;
                    }
                }
            }
        }

        return null;
    }

    /**
     * Search for annotations of the specified {@code annotationName} or
     * {@code annotationType} on the specified {@code element}, following
     * <em>find semantics</em>.
     *
     * @param element the annotated element
     * @param annotationType the annotation type to find
     * @param annotationName the fully qualified class name of the annotation
     * type to find (as an alternative to {@code annotationType})
     * @param processor the processor to delegate to
     * @return the result of the processor, potentially {@code null}
     * @since 4.2
     */
    @Nullable
    private static <T> T searchWithFindSemantics(AnnotatedElement element,
            @Nullable Class<? extends Annotation> annotationType,
            @Nullable String annotationName, Processor<T> processor) {

        return searchWithFindSemantics(element, annotationType, annotationName, null, processor);
    }

    /**
     * Search for annotations of the specified {@code annotationName} or
     * {@code annotationType} on the specified {@code element}, following
     * <em>find semantics</em>.
     *
     * @param element the annotated element
     * @param annotationType the annotation type to find
     * @param annotationName the fully qualified class name of the annotation
     * type to find (as an alternative to {@code annotationType})
     * @param containerType the type of the container that holds repeatable
     * annotations, or {@code null} if the annotation is not repeatable
     * @param processor the processor to delegate to
     * @return the result of the processor, potentially {@code null}
     * @since 4.3
     */
    @Nullable
    private static <T> T searchWithFindSemantics(AnnotatedElement element,
            @Nullable Class<? extends Annotation> annotationType, @Nullable String annotationName,
            @Nullable Class<? extends Annotation> containerType, Processor<T> processor) {

        if (containerType != null && !processor.aggregates()) {
            throw new IllegalArgumentException(
                    "Searches for repeatable annotations must supply an aggregating Processor");
        }

        try {
            return searchWithFindSemantics(
                    element, annotationType, annotationName, containerType, processor, new HashSet<>(), 0);
        } catch (Throwable ex) {
            AnnotationUtils.rethrowAnnotationConfigurationException(ex);
            throw new IllegalStateException("Failed to introspect annotations on " + element, ex);
        }
    }

    /**
     * Perform the search algorithm for the {@link #searchWithFindSemantics}
     * method, avoiding endless recursion by tracking which annotated elements
     * have already been <em>visited</em>.
     * <p>The {@code metaDepth} parameter is explained in the
     * {@link Processor#process process()} method of the {@link Processor} API.
     *
     * @param element the annotated element (never {@code null})
     * @param annotationType the annotation type to find
     * @param annotationName the fully qualified class name of the annotation
     * type to find (as an alternative to {@code annotationType})
     * @param containerType the type of the container that holds repeatable
     * annotations, or {@code null} if the annotation is not repeatable
     * @param processor the processor to delegate to
     * @param visited the set of annotated elements that have already been visited
     * @param metaDepth the meta-depth of the annotation
     * @return the result of the processor, potentially {@code null}
     * @since 4.2
     */
    @Nullable
    private static <T> T searchWithFindSemantics(AnnotatedElement element,
            @Nullable Class<? extends Annotation> annotationType, @Nullable String annotationName,
            @Nullable Class<? extends Annotation> containerType, Processor<T> processor,
            Set<AnnotatedElement> visited, int metaDepth) {

        if (visited.add(element)) {
            try {
                // Locally declared annotations (ignoring @Inherited)
                Annotation[] annotations = element.getDeclaredAnnotations();
                List<T> aggregatedResults = (processor.aggregates() ? new ArrayList<>() : null);

                // Search in local annotations
                for (Annotation annotation : annotations) {
                    Class<? extends Annotation> currentAnnotationType = annotation.annotationType();
                    if (!AnnotationUtils.isInJavaLangAnnotationPackage(currentAnnotationType)) {
                        if (currentAnnotationType == annotationType ||
                                currentAnnotationType.getName().equals(annotationName) ||
                                processor.alwaysProcesses()) {
                            T result = processor.process(element, annotation, metaDepth);
                            if (result != null) {
                                if (aggregatedResults != null && metaDepth == 0) {
                                    aggregatedResults.add(result);
                                } else {
                                    return result;
                                }
                            }
                        }
                        // Repeatable annotations in container?
                        else if (currentAnnotationType == containerType) {
                            for (Annotation contained : getRawAnnotationsFromContainer(element, annotation)) {
                                T result = processor.process(element, contained, metaDepth);
                                if (aggregatedResults != null && result != null) {
                                    // No need to post-process since repeatable annotations within a
                                    // container cannot be composed annotations.
                                    aggregatedResults.add(result);
                                }
                            }
                        }
                    }
                }

                // Search in meta annotations on local annotations
                for (Annotation annotation : annotations) {
                    Class<? extends Annotation> currentAnnotationType = annotation.annotationType();
                    if (!AnnotationUtils.isInJavaLangAnnotationPackage(currentAnnotationType)) {
                        T result = searchWithFindSemantics(currentAnnotationType, annotationType, annotationName,
                                containerType, processor, visited, metaDepth + 1);
                        if (result != null) {
                            processor.postProcess(currentAnnotationType, annotation, result);
                            if (aggregatedResults != null && metaDepth == 0) {
                                aggregatedResults.add(result);
                            } else {
                                return result;
                            }
                        }
                    }
                }

                if (aggregatedResults != null) {
                    // Prepend to support top-down ordering within class hierarchies
                    processor.getAggregatedResults().addAll(0, aggregatedResults);
                }

                if (element instanceof Method) {
                    Method method = (Method) element;

                    // Search on possibly bridged method
                    Method resolvedMethod = BridgeMethodResolver.findBridgedMethod(method);
                    T result = searchWithFindSemantics(resolvedMethod, annotationType, annotationName, containerType,
                            processor, visited, metaDepth);
                    if (result != null) {
                        return result;
                    }

                    // Search on methods in interfaces declared locally
                    Class<?>[] ifcs = method.getDeclaringClass().getInterfaces();
                    result = searchOnInterfaces(method, annotationType, annotationName, containerType, processor,
                            visited, metaDepth, ifcs);
                    if (result != null) {
                        return result;
                    }

                    // Search on methods in class hierarchy and interface hierarchy
                    Class<?> clazz = method.getDeclaringClass();
                    while (true) {
                        clazz = clazz.getSuperclass();
                        if (clazz == null || Object.class == clazz) {
                            break;
                        }

                        try {
                            Method equivalentMethod =
                                    clazz.getDeclaredMethod(method.getName(), method.getParameterTypes());
                            Method resolvedEquivalentMethod = BridgeMethodResolver.findBridgedMethod(equivalentMethod);
                            result = searchWithFindSemantics(resolvedEquivalentMethod, annotationType, annotationName,
                                    containerType, processor, visited, metaDepth);
                            if (result != null) {
                                return result;
                            }
                        } catch (NoSuchMethodException ex) {
                            // No equivalent method found
                        }

                        // Search on interfaces declared on superclass
                        result = searchOnInterfaces(method, annotationType, annotationName, containerType, processor,
                                visited, metaDepth, clazz.getInterfaces());
                        if (result != null) {
                            return result;
                        }
                    }
                } else if (element instanceof Class) {
                    Class<?> clazz = (Class<?>) element;

                    // Search on interfaces
                    for (Class<?> ifc : clazz.getInterfaces()) {
                        T result = searchWithFindSemantics(ifc, annotationType, annotationName, containerType,
                                processor, visited, metaDepth);
                        if (result != null) {
                            return result;
                        }
                    }

                    // Search on superclass
                    Class<?> superclass = clazz.getSuperclass();
                    if (superclass != null && Object.class != superclass) {
                        T result = searchWithFindSemantics(superclass, annotationType, annotationName, containerType,
                                processor, visited, metaDepth);
                        if (result != null) {
                            return result;
                        }
                    }
                }
            } catch (Throwable ex) {
                AnnotationUtils.handleIntrospectionFailure(element, ex);
            }
        }
        return null;
    }

    @Nullable
    private static <T> T searchOnInterfaces(Method method, @Nullable Class<? extends Annotation> annotationType,
            @Nullable String annotationName, @Nullable Class<? extends Annotation> containerType,
            Processor<T> processor, Set<AnnotatedElement> visited, int metaDepth, Class<?>[] ifcs) {

        for (Class<?> iface : ifcs) {
            if (AnnotationUtils.isInterfaceWithAnnotatedMethods(iface)) {
                try {
                    Method equivalentMethod = iface.getMethod(method.getName(), method.getParameterTypes());
                    T result = searchWithFindSemantics(equivalentMethod, annotationType, annotationName, containerType,
                            processor, visited, metaDepth);
                    if (result != null) {
                        return result;
                    }
                } catch (NoSuchMethodException ex) {
                    // Skip this interface - it doesn't have the method...
                }
            }
        }

        return null;
    }

    /**
     * Get the array of raw (unsynthesized) annotations from the {@code value}
     * attribute of the supplied repeatable annotation {@code container}.
     *
     * @since 4.3
     */
    @SuppressWarnings("unchecked")
    private static <A extends Annotation> A[] getRawAnnotationsFromContainer(
            @Nullable AnnotatedElement element, Annotation container) {

        try {
            A[] value = (A[]) AnnotationUtils.getValue(container);
            if (value != null) {
                return value;
            }
        } catch (Throwable ex) {
            AnnotationUtils.handleIntrospectionFailure(element, ex);
        }
        // Unable to read value from repeating annotation container -> ignore it.
        return (A[]) EMPTY_ANNOTATION_ARRAY;
    }

    /**
     * Resolve the container type for the supplied repeatable {@code annotationType}.
     * <p>Delegates to {@link AnnotationUtils#resolveContainerAnnotationType(Class)}.
     *
     * @param annotationType the annotation type to resolve the container for
     * @return the container type (never {@code null})
     * @throws IllegalArgumentException if the container type cannot be resolved
     * @since 4.3
     */
    private static Class<? extends Annotation> resolveContainerType(Class<? extends Annotation> annotationType) {
        Class<? extends Annotation> containerType = AnnotationUtils.resolveContainerAnnotationType(annotationType);
        if (containerType == null) {
            throw new IllegalArgumentException(
                    "Annotation type must be a repeatable annotation: failed to resolve container type for " +
                            annotationType.getName());
        }
        return containerType;
    }

    /**
     * Validate that the supplied {@code containerType} is a proper container
     * annotation for the supplied repeatable {@code annotationType} (i.e.,
     * that it declares a {@code value} attribute that holds an array of the
     * {@code annotationType}).
     *
     * @throws AnnotationConfigurationException if the supplied {@code containerType}
     * is not a valid container annotation for the supplied {@code annotationType}
     * @since 4.3
     */
    private static void validateContainerType(Class<? extends Annotation> annotationType,
            Class<? extends Annotation> containerType) {

        try {
            Method method = containerType.getDeclaredMethod(AnnotationUtils.VALUE);
            Class<?> returnType = method.getReturnType();
            if (!returnType.isArray() || returnType.getComponentType() != annotationType) {
                String msg = String.format(
                        "Container type [%s] must declare a 'value' attribute for an array of type [%s]",
                        containerType.getName(), annotationType.getName());
                throw new AnnotationConfigurationException(msg);
            }
        } catch (Throwable ex) {
            AnnotationUtils.rethrowAnnotationConfigurationException(ex);
            String msg = String.format("Invalid declaration of container type [%s] for repeatable annotation [%s]",
                    containerType.getName(), annotationType.getName());
            throw new AnnotationConfigurationException(msg, ex);
        }
    }

    /**
     * @since 4.3
     */
    private static <A extends Annotation> Set<A> postProcessAndSynthesizeAggregatedResults(AnnotatedElement element,
            Class<A> annotationType, List<AnnotationAttributes> aggregatedResults) {

        Set<A> annotations = new LinkedHashSet<>();
        for (AnnotationAttributes attributes : aggregatedResults) {
            AnnotationUtils.postProcessAnnotationAttributes(element, attributes, false, false);
            annotations.add(AnnotationUtils.synthesizeAnnotation(attributes, annotationType, element));
        }
        return annotations;
    }


    /**
     * Callback interface that is used to process annotations during a search.
     * <p>Depending on the use case, a processor may choose to
     * {@linkplain #process} a single target annotation, multiple target
     * annotations, or all annotations discovered by the currently executing
     * search. The term "target" in this context refers to a matching
     * annotation (i.e., a specific annotation type that was found during
     * the search).
     * <p>Returning a non-null value from the {@link #process}
     * method instructs the search algorithm to stop searching further;
     * whereas, returning {@code null} from the {@link #process} method
     * instructs the search algorithm to continue searching for additional
     * annotations. One exception to this rule applies to processors
     * that {@linkplain #aggregates aggregate} results. If an aggregating
     * processor returns a non-null value, that value will be added to the
     * list of {@linkplain #getAggregatedResults aggregated results}
     * and the search algorithm will continue.
     * <p>Processors can optionally {@linkplain #postProcess post-process}
     * the result of the {@link #process} method as the search algorithm
     * goes back down the annotation hierarchy from an invocation of
     * {@link #process} that returned a non-null value down to the
     * {@link AnnotatedElement} that was supplied as the starting point to
     * the search algorithm.
     *
     * @param <T> the type of result returned by the processor
     */
    private interface Processor<T> {

        /**
         * Process the supplied annotation.
         * <p>The supplied annotation will be an actual target annotation
         * that has been found by the search algorithm, unless this processor
         * is configured to {@linkplain #alwaysProcesses always process}
         * annotations in which case it may be some other annotation within an
         * annotation hierarchy. In the latter case, the {@code metaDepth}
         * will have a value greater than {@code 0}. In any case, it is
         * up to concrete implementations of this method to decide what to
         * do with the supplied annotation.
         * <p>The {@code metaDepth} parameter represents the depth of the
         * annotation relative to the first annotated element in the
         * annotation hierarchy. For example, an annotation that is
         * <em>present</em> on a non-annotation element will have a depth
         * of 0; a meta-annotation will have a depth of 1; and a
         * meta-meta-annotation will have a depth of 2; etc.
         *
         * @param annotatedElement the element that is annotated with the
         * supplied annotation, used for contextual logging; may be
         * {@code null} if unknown
         * @param annotation the annotation to process
         * @param metaDepth the meta-depth of the annotation
         * @return the result of the processing, or {@code null} to continue
         * searching for additional annotations
         */
        @Nullable
        T process(@Nullable AnnotatedElement annotatedElement, Annotation annotation, int metaDepth);

        /**
         * Post-process the result returned by the {@link #process} method.
         * <p>The {@code annotation} supplied to this method is an annotation
         * that is present in the annotation hierarchy, between the initial
         * {@link AnnotatedElement} and an invocation of {@link #process}
         * that returned a non-null value.
         *
         * @param annotatedElement the element that is annotated with the
         * supplied annotation, used for contextual logging; may be
         * {@code null} if unknown
         * @param annotation the annotation to post-process
         * @param result the result to post-process
         */
        void postProcess(@Nullable AnnotatedElement annotatedElement, Annotation annotation, T result);

        /**
         * Determine if this processor always processes annotations regardless of
         * whether or not the target annotation has been found.
         *
         * @return {@code true} if this processor always processes annotations
         * @since 4.3
         */
        boolean alwaysProcesses();

        /**
         * Determine if this processor aggregates the results returned by {@link #process}.
         * <p>If this method returns {@code true}, then {@link #getAggregatedResults()}
         * must return a non-null value.
         *
         * @return {@code true} if this processor supports aggregated results
         * @see #getAggregatedResults
         * @since 4.3
         */
        boolean aggregates();

        /**
         * Get the list of results aggregated by this processor.
         * <p>NOTE: the processor does <strong>not</strong> aggregate the results
         * itself. Rather, the search algorithm that uses this processor is
         * responsible for asking this processor if it {@link #aggregates} results
         * and then adding the post-processed results to the list returned by this
         * method.
         *
         * @return the list of results aggregated by this processor (never {@code null})
         * @see #aggregates
         * @since 4.3
         */
        List<T> getAggregatedResults();
    }


    /**
     * {@link Processor} that {@linkplain #process(AnnotatedElement, Annotation, int)
     * processes} annotations but does not {@linkplain #postProcess post-process} or
     * {@linkplain #aggregates aggregate} results.
     *
     * @since 4.2
     */
    private abstract static class SimpleAnnotationProcessor<T> implements Processor<T> {

        private final boolean alwaysProcesses;

        public SimpleAnnotationProcessor() {
            this(false);
        }

        public SimpleAnnotationProcessor(boolean alwaysProcesses) {
            this.alwaysProcesses = alwaysProcesses;
        }

        @Override
        public final boolean alwaysProcesses() {
            return this.alwaysProcesses;
        }

        @Override
        public final void postProcess(@Nullable AnnotatedElement annotatedElement, Annotation annotation, T result) {
            // no-op
        }

        @Override
        public final boolean aggregates() {
            return false;
        }

        @Override
        public final List<T> getAggregatedResults() {
            throw new UnsupportedOperationException("SimpleAnnotationProcessor does not support aggregated results");
        }
    }


    /**
     * {@link SimpleAnnotationProcessor} that always returns {@link Boolean#TRUE} when
     * asked to {@linkplain #process(AnnotatedElement, Annotation, int) process} an
     * annotation.
     *
     * @since 4.3
     */
    static class AlwaysTrueBooleanAnnotationProcessor extends SimpleAnnotationProcessor<Boolean> {

        @Override
        public final Boolean process(@Nullable AnnotatedElement annotatedElement, Annotation annotation,
                int metaDepth) {
            return Boolean.TRUE;
        }
    }


    /**
     * {@link Processor} that gets the {@code AnnotationAttributes} for the
     * target annotation during the {@link #process} phase and then merges
     * annotation attributes from lower levels in the annotation hierarchy
     * during the {@link #postProcess} phase.
     * <p>A {@code MergedAnnotationAttributesProcessor} may optionally be
     * configured to {@linkplain #aggregates aggregate} results.
     *
     * @see AnnotationUtils#retrieveAnnotationAttributes
     * @see AnnotationUtils#postProcessAnnotationAttributes
     * @since 4.2
     */
    private static class MergedAnnotationAttributesProcessor implements Processor<AnnotationAttributes> {

        private final boolean classValuesAsString;

        private final boolean nestedAnnotationsAsMap;

        private final boolean aggregates;

        private final List<AnnotationAttributes> aggregatedResults;

        MergedAnnotationAttributesProcessor() {
            this(false, false, false);
        }

        MergedAnnotationAttributesProcessor(boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
            this(classValuesAsString, nestedAnnotationsAsMap, false);
        }

        MergedAnnotationAttributesProcessor(boolean classValuesAsString, boolean nestedAnnotationsAsMap,
                boolean aggregates) {

            this.classValuesAsString = classValuesAsString;
            this.nestedAnnotationsAsMap = nestedAnnotationsAsMap;
            this.aggregates = aggregates;
            this.aggregatedResults = (aggregates ? new ArrayList<>() : Collections.emptyList());
        }

        @Override
        public boolean alwaysProcesses() {
            return false;
        }

        @Override
        public boolean aggregates() {
            return this.aggregates;
        }

        @Override
        public List<AnnotationAttributes> getAggregatedResults() {
            return this.aggregatedResults;
        }

        @Override
        @Nullable
        public AnnotationAttributes process(@Nullable AnnotatedElement annotatedElement, Annotation annotation,
                int metaDepth) {
            return AnnotationUtils.retrieveAnnotationAttributes(annotatedElement, annotation,
                    this.classValuesAsString, this.nestedAnnotationsAsMap);
        }

        @Override
        public void postProcess(@Nullable AnnotatedElement element, Annotation annotation,
                AnnotationAttributes attributes) {
            annotation = AnnotationUtils.synthesizeAnnotation(annotation, element);
            Class<? extends Annotation> targetAnnotationType = attributes.annotationType();

            // Track which attribute values have already been replaced so that we can short
            // circuit the search algorithms.
            Set<String> valuesAlreadyReplaced = new HashSet<>();

            for (Method attributeMethod : AnnotationUtils.getAttributeMethods(annotation.annotationType())) {
                String attributeName = attributeMethod.getName();
                String attributeOverrideName =
                        AnnotationUtils.getAttributeOverrideName(attributeMethod, targetAnnotationType);

                // Explicit annotation attribute override declared via @AliasFor
                if (attributeOverrideName != null) {
                    if (valuesAlreadyReplaced.contains(attributeOverrideName)) {
                        continue;
                    }

                    List<String> targetAttributeNames = new ArrayList<>();
                    targetAttributeNames.add(attributeOverrideName);
                    valuesAlreadyReplaced.add(attributeOverrideName);

                    // Ensure all aliased attributes in the target annotation are overridden. (SPR-14069)
                    List<String> aliases =
                            AnnotationUtils.getAttributeAliasMap(targetAnnotationType).get(attributeOverrideName);
                    if (aliases != null) {
                        for (String alias : aliases) {
                            if (!valuesAlreadyReplaced.contains(alias)) {
                                targetAttributeNames.add(alias);
                                valuesAlreadyReplaced.add(alias);
                            }
                        }
                    }

                    overrideAttributes(element, annotation, attributes, attributeName, targetAttributeNames);
                }
                // Implicit annotation attribute override based on convention
                else if (!AnnotationUtils.VALUE.equals(attributeName) && attributes.containsKey(attributeName)) {
                    overrideAttribute(element, annotation, attributes, attributeName, attributeName);
                }
            }
        }

        private void overrideAttributes(@Nullable AnnotatedElement element, Annotation annotation,
                AnnotationAttributes attributes, String sourceAttributeName, List<String> targetAttributeNames) {

            Object adaptedValue = getAdaptedValue(element, annotation, sourceAttributeName);

            for (String targetAttributeName : targetAttributeNames) {
                attributes.put(targetAttributeName, adaptedValue);
            }
        }

        private void overrideAttribute(@Nullable AnnotatedElement element, Annotation annotation,
                AnnotationAttributes attributes, String sourceAttributeName, String targetAttributeName) {

            attributes.put(targetAttributeName, getAdaptedValue(element, annotation, sourceAttributeName));
        }

        @Nullable
        private Object getAdaptedValue(
                @Nullable AnnotatedElement element, Annotation annotation, String sourceAttributeName) {

            Object value = AnnotationUtils.getValue(annotation, sourceAttributeName);
            return AnnotationUtils.adaptValue(element, value, this.classValuesAsString, this.nestedAnnotationsAsMap);
        }
    }

}
